#include "CentralCache.h"
#include "PageCache.h"

CentralCache CentralCache::s_inst; // 一定要在源文件中定义

size_t CentralCache::FetchRangeObj(void *&start, void *&end, size_t alineSize, size_t batchNum)
{
    // 根据申请的内存块大小计算对应的桶号(spanlist下标)
    size_t index = AlineRule::GetIndex(alineSize);

    _spanLists[index].Lock();                              // 访问特定的桶时要加锁
    Span *span = GetOneSpan(_spanLists[index], alineSize); // 找到一个非空的span，如果没有就要去PageCache中申请
    assert(span != nullptr);
    assert(span->_freeList != nullptr);

    // 从span中取一批内存，能取多少取多少
    start = span->_freeList;
    void *cur = start;
    size_t realNum = 0;
    size_t i = 0;
    while (i < batchNum && cur != nullptr)
    {
        end = cur;
        cur = NextObj(cur);
        ++i;
        ++realNum;
    }
    NextObj(end) = nullptr;
    span->_freeList = cur;
    span->_useCount += realNum;

    _spanLists[index].Unlock();

    // cout << std::this_thread::get_id() << " FetchFromCentralCache!" << " alinesize: " << alineSize
    // << " realNum: " << realNum << " spanUsecount: " << span->_useCount << endl; // debug

    return realNum;
}

Span *CentralCache::GetOneSpan(SpanList &list, size_t alineSize)
{
    // 遍历spanlist，找到一个非空的span就返回
    Span *cur = list.Begin();
    while (cur != list.End())
    {
        if (cur->_freeList != nullptr)
        {
            return cur;
        }
        cur = cur->_next;
    }

    // 如果没有，就需要向PageCache申请页内存了，得到的是一个span
    // 先将桶锁释放，使向该桶的内存释放可以并行执行
    list.Unlock();
    Span *span = PageCache::GetInstance()->NewSpan(AlineRule::SizeMovePage(alineSize));
    // cout << std::this_thread::get_id() << ": NewSpan(" << span << ") pageId: "
    //      << span->_pageId << " pageNum: " << span->_pageNum << endl; // debug
    assert(span != nullptr);

    // 将申请到的span页内存分割成小块内存
    // 注意！分割内存块时不需要加锁，因为此时的span还没有链入到桶中，不能被其他线程访问到！
    char *start = (char *)(span->_pageId << PAGE_SHIFT); // 计算页内存首地址
    size_t bytes = span->_pageNum << PAGE_SHIFT;         // 页内存大小（字节）
    char *end = start + bytes;                           // 为什么用char*？因为指针要以字节为单位移动

    span->_freeList = start;
    span->_objSize = alineSize;
    start += alineSize;
    void *tail = span->_freeList;
    while (start < end)
    {
        NextObj(tail) = start;
        tail = start;
        start += alineSize;
    }
    NextObj(tail) = nullptr; // 注意！最后的尾节点指针指向空

    // 最后将完成分割的span挂接到spanlist当中
    // 链入spanlist时就需要加锁了！
    list.Lock();
    list.PushFront(span);

    return span;
}

void CentralCache::ReleaseListToSpans(void *start, size_t size)
{
    size_t index = AlineRule::GetIndex(size);
    // 遍历list，由地址->页号->span*，归还到对应的span
    _spanLists[index].Lock();
    // size_t cnt = 0; // debug
    // size_t tmp = 0; // debug
    while (start != nullptr)
    {
        // ++cnt; // debug
        void *next = NextObj(start);
        Span *pspan = PageCache::GetInstance()->MapObjectToSpan(start);
        // 将内存块头插到对应span的freelist
        NextObj(start) = pspan->_freeList;
        pspan->_freeList = start;
        --pspan->_useCount;
        // tmp = --pspan->_useCount; // debug
        // 如果对应span的usecount减到0，说明所有内存归位，则继续释放给PageCache
        if (pspan->_useCount == 0)
        {
            _spanLists[index].Erase(pspan); // 断开连接关系
            pspan->_freeList = nullptr;
            pspan->_prev = pspan->_next = nullptr;
            // cout << std::this_thread::get_id() << ": ReleaseSpan(" << pspan << ") pageId: "
            //      << pspan->_pageId << " pageNum: " << pspan->_pageNum << endl; // debug
            _spanLists[index].Unlock();
            PageCache::GetInstance()->ReleaseSpanToPageCache(pspan);
            _spanLists[index].Lock();
        }
        start = next;
    }
    _spanLists[index].Unlock();
    // cout << std::this_thread::get_id() << " ReleaseToCentralCache!" << " alinesize: " << size
    // << " num: " << cnt << " spanUsecount: " << tmp << endl; // debug
}
